/**
 * Advanced Software Engineering (Monica Farrow)
 * CustomerQueue class
 * 
 * Some of the methods in this class have been based on lecture notes provided by
 * Monica Farrow as a part of Software Engineering courses at Heriot-Watt University.
 */

package depotModels;

import java.util.*;

import depot.Customer;
import depot.Parcel;
import depotAdmin.*;
import depotInterfaces.DepotObserver;
import depotInterfaces.DepotSubject;

/**
 * CustomerQueue class - contains collection of customers awaiting processing
 * and methods to manipulate the collection
 * @author Ionut Iordache, Nikky Afunugo, Dave Bond
 */
public class CustomerQueue implements DepotSubject {

	// holds customers awaiting processing
	private LinkedList<Customer> custQueue; 
	// holds list of observers
	private List<DepotObserver> registeredObservers = new LinkedList<DepotObserver>();
	// indicates whether custQueue is empty or not
	private Boolean custInQueue = false; 
	// stores log entries
	private String eventLogEntry = "";  

	/** 
	 * constructor for CustomerQueue
	 * creates an empty LinkedList for object type Customer
	 */ 
	public CustomerQueue() { 
		custQueue = new LinkedList<Customer>(); 
	} 

	/** 
	 * takes ID and iterates through custQueue
	 * then returns the Customer object with matching ID
	 * @param id	the ID of the customer
	 * @return the customer or null
	 */
	public Customer getCustomerByID(String id) {
		for (Customer cust : custQueue) {
			if (cust.getCustID().equalsIgnoreCase(id))
			{
				return cust;
			}
		}
		return null;
	}

	/** 
	 * obtains the first Customer in custQueue
	 * used by ProcessingController
	 * @return	the Customer or null
	 */ 
	public synchronized Customer getFirst() { 
		Customer cust = null;

		// if queue empty, wait
		while(! custInQueue) {
			try {
				wait();
			} catch (InterruptedException ie) {
			}
		}

		try{
			cust = custQueue.getFirst();
		} catch (NoSuchElementException nse) {
			cust = null;
			return cust;
		}
		return cust;
	} 

	/** 
	 * obtains the most recently added Customer in custQueue
	 * used by observer to update display
	 * @return	the Customer or null
	 */ 
	public synchronized Customer getLast() { 
		Customer cust = null;

		try{
			cust = custQueue.getLast();
		} catch (NoSuchElementException nse) {
			cust = null;
			return cust;
		}
		return cust;
	} 

	/** 
	 * removes a Customer from custQueue
	 * @param cust   the customer to be removed
	 * @param end   whether it's the end of the day (tidying up)
	 * @return	Boolean indicating success or failure
	 */ 
	public Boolean removeFromQueue(Customer cust, Boolean end) { 
		Boolean custRemoved = false;
		custRemoved = custQueue.remove(cust);

		// start eventLog entry
		eventLogEntry = "<Cus> Customer " + cust.getCustID();

		if(! end) {
			eventLogEntry += " removed from CustomerQueue\r\n";
		}

		else {
			eventLogEntry += " ejected from depot\r\n";
		}

		// notify GUI of change
		notifyObservers("rem");

		// finish eventLog entry
		writeToLog("eventLog.txt",eventLogEntry,"append");

		return custRemoved;
	} 

	/** 
	 * adds the Customer in the parameter to custQueue
	 * @param	cust   the Customer to be added
	 */ 
	public synchronized void addToQueue(Customer cust) { 
		custQueue.addLast(cust);

		// set custInQueue to true and notify waiting threads
		setCustInQueue(true);
		notifyAll(); 

		// notify GUI of change
		notifyObservers("add");

		// eventLog entry
		eventLogEntry += "<Cus> Customer " + cust.getCustID() + " added to CustomerQueue\r\n";
		writeToLog("eventLog.txt",eventLogEntry,"append");
	} 

	/**
	 * checks state of custInQueue variable
	 * @return Boolean custInQueue 
	 */
	public Boolean getCustInQueue() {
		return custInQueue;
	}


	/**
	 * sets state of custInQueue variable to that contained in parameter
	 * @param ciq   Boolean object
	 */
	public void setCustInQueue(Boolean ciq) {
		this.custInQueue = ciq;
	}


	/**
	 * Processes each line from processInputFIle, extracts data,
	 * creates Customer object and adds to custQueue.
	 * Each line contains: ID, first name, last name, parcelID (not used)
	 * @param pcl   Parcel object belonging to customer
	 * @param line   the line to be processed
	 * @param lineNum   the line number
	 * @return eventLog entry
	 * @throws IllegalArgumentException, DuplicateIDException, ArrayIndexOutOfBoundsException 
	 */
	public synchronized void processLine(Parcel pcl, String line, int lineNum) throws IllegalArgumentException, DuplicateIDException, ArrayIndexOutOfBoundsException {

		try {
			Thread.sleep(DepotUtils.getInitAndTidyUpSpeed());
		} catch (InterruptedException e) {
		} 

		String details [] = line.split(",");

		// take id
		String id = details[0];
		id = id.trim();

		// checks custQueue for duplicate ID
		Customer cust99 = getCustomerByID(id);
		if(cust99 != null) {
			throw new DuplicateIDException("CustomerID "+id+" already exists in customer queue");
		}

		// take first name
		String fName = details[1];

		// take last name
		String lName = details[2];

		// take parcelID
		String pID = details[3];

		// creates new Customer object and adds to custQueue
		Customer cust = new Customer(id, fName, lName, pcl);
		addToQueue(cust);

	}


	/**
	 * sets value of DepotUtils.procLoopFinished variable to true
	 * and notifies waiting threads (customer thread)
	 */
	public synchronized void setProcFinishedFlag() {

		// set flag
		DepotUtils.setProcLoopFinished(true);

		// notify any waiting threads
		notifyAll();
	} 


	/**
	 * step 1 of setting value of DepotUtils.custLoopFinished to true
	 * check that procLoopFinished=true
	 */
	public synchronized void setCustFinishedFlagPt1() {

		while(! DepotUtils.getProcLoopFinished()) {
			try { // if processing thread not yet finished, wait
				wait();
			} catch (InterruptedException ie) {
			}
		}

	} 


	/**
	 * takes customers from custQueue
	 * and returns them in an ArrayList
	 * used by ProcessingController to tidy up
	 * @return  the ArrayList of customers
	 */
	public ArrayList<Customer> getCustsLeftInQueue() {
		ArrayList<Customer> leftInQueue = new ArrayList<Customer>();
		for(Customer cust : custQueue) {
			leftInQueue.add(cust);
		}

		return leftInQueue;
	} 


	/**
	 * passes event log entry to TextWriter to be written to file
	 * @param filename  the filename to write to
	 * @param report  the text to write
	 * @param mode  information about the text, used by TextWriter
	 */
	public void writeToLog(String filename, String report, String mode) {
		TextWriter tw = TextWriter.getInstance();
		tw.writeToFile(filename, report, mode);
		eventLogEntry = "";
	}


	/**
	 * METHODS FOR DEPOTSUBJECT INTERFACE
	 */

	/**
	 * Adds a DepotObserver to the list of registered observers
	 * @param obs   the DepotObserver object
	 */
	public void registerObserver(DepotObserver obs) {
		registeredObservers.add(obs);	}

	/**
	 * Removes a DepotObserver from the list of registered observers
	 * @param obs   the DepotObserver object
	 */
	public void removeObserver(DepotObserver obs) {
		registeredObservers.remove(obs);	}

	/**
	 * Notifies all DepotObservers in the list of registered observers
	 * that a change to the data has occurred
	 * @param info   String information about the type of data change
	 */
	public void notifyObservers(String info) {
		for( DepotObserver obs : registeredObservers) {
			// call observer's update method to retrieve data
			obs.update(info); 
		} 
	} 

}
